/*
This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin.

  Keepass2Android is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  Keepass2Android is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with Keepass2Android.  If not, see <http://www.gnu.org/licenses/>.
  */

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using Android.App;
using Android.Content;
using Android.Database;
using Android.OS;
using Android.Preferences;
using Android.Provider;
using Android.Views;
using Android.Widget;
using Android.Content.PM;
using Android.Content.Res;
using Android.Graphics;
using Android.Graphics.Drawables;
using Android.Hardware.Display;
using Android.Util;
using Android.Views.InputMethods;
using AndroidX.Core.View;
using AndroidX.Core.View.InputMethod;
using AndroidX.ViewPager2.Widget;
using Google.Android.Material.Dialog;
using KeePass.Util;
using keepass2android;
using KeePassLib;
using KeePassLib.Security;
using KeePassLib.Serialization;
using Org.Apache.Http.Util;
using Uri = Android.Net.Uri;


namespace keepass2android
{
  public class LocaleManager
  {
    public static Context LocalizedAppContext
    {
      get
      {
        //the standard way of setting locale on the app context is through Application.AttachBaseContext,
        //but because of https://bugzilla.xamarin.com/11/11182/bug.html this doesn't work
        return setLocale(Application.Context);
      }
    }
    public static Context setLocale(Context c)
    {
      return setNewLocale(c, getLanguage(c));
    }

    public static Context setNewLocale(Context c, String language)
    {
      return updateResources(c, language);
    }

    static string _language;
    public static string Language
    {
      get { return _language; }
      set
      {
        _language = value; languageLoaded = true;
      }
    }
    static bool languageLoaded = false;

    public static String getLanguage(Context c)
    {
      if (!languageLoaded)
      {
        var langPref = PreferenceManager.GetDefaultSharedPreferences(c).GetString(c.GetString(Resource.String.app_language_pref_key), null);
        Language = LanguageEntry.PrefCodeToLanguage(langPref);
      }
      return Language;
    }

    private static Context updateResources(Context context, String language)
    {
      if (language == null)
        return context;

      Java.Util.Locale locale = new Java.Util.Locale(language);
      Java.Util.Locale.Default = locale;

      Resources res = context.Resources;
      Configuration config = new Configuration(res.Configuration);
      if ((int)Build.VERSION.SdkInt >= 17)
      {
        config.SetLocale(locale);
        context = context.CreateConfigurationContext(config);
      }
      else
      {
        config.Locale = locale;
        res.UpdateConfiguration(config, res.DisplayMetrics);
      }
      return context;

    }
  }

  /// <summary>
  /// A small container/wrapper that helps with the building and sorting of the 
  /// Language Preference list, and interoperability between it and LocaleManager
  /// </summary>
  class LanguageEntry
  {
    // "System language" preference code; maps to LocaleManager.Language = null
    private const string SYS_LANG_CODE = "SysLang";

    // Preference code (2-char lowercase, or SYS_LANG_CODE)
    public readonly string Code;
    // Localized display name
    public readonly string Name;
    // True if this LanguageEntry is the "System language", false otherwise
    public readonly bool IsSystem;

    /// <summary>
    /// Creates a LanguageEntry from a Locale
    /// </summary>
    /// <param name="from"></param>
    /// <returns></returns>
    public static LanguageEntry OfLocale(Java.Util.Locale from)
    {
      return new LanguageEntry(from.Language, from.DisplayLanguage, false);
    }

    /// <summary>
    /// Creates the "System language" LanguageEntry with the given localized display name,
    /// special preference code, marked as a System entry.
    /// </summary>
    /// <param name="displayName"></param>
    /// <returns></returns>
    public static LanguageEntry SystemDefault(string displayName)
    {
      return new LanguageEntry(SYS_LANG_CODE, displayName, true);
    }

    private LanguageEntry(string code, string name, bool isSystem)
    {
      this.Code = code;
      this.Name = name;
      this.IsSystem = isSystem;
    }

    /// <summary>
    /// Converts a language preference code to a code that is compatible with LocaleManager.Language
    /// </summary>
    /// <param name="code"></param>
    /// <returns>A converted code, possibly null</returns>
    public static string PrefCodeToLanguage(string code)
    {
      return string.IsNullOrEmpty(code) || SYS_LANG_CODE.Equals(code) ? null : code;
    }

    public override string ToString()
    {
      return "{" + this.Code + ":" + this.Name + "}";
    }
  }


  public class Util
  {


    public const String KeyFilename = "fileName";
    public const String KeyServerusername = "serverCredUser";
    public const String KeyServerpassword = "serverCredPwd";
    public const String KeyServercredmode = "serverCredRememberMode";


    public static void PutIoConnectionToIntent(IOConnectionInfo ioc, Intent i, string prefix = "")
    {
      i.PutExtra(prefix + KeyFilename, ioc.Path);
      i.PutExtra(prefix + KeyServerusername, ioc.UserName);
      i.PutExtra(prefix + KeyServerpassword, ioc.Password);
      i.PutExtra(prefix + KeyServercredmode, (int)ioc.CredSaveMode);
    }

    public static void SetIoConnectionFromIntent(IOConnectionInfo ioc, Intent i, string prefix = "")
    {
      ioc.Path = i.GetStringExtra(prefix + KeyFilename);
      ioc.UserName = i.GetStringExtra(prefix + KeyServerusername) ?? "";
      ioc.Password = i.GetStringExtra(prefix + KeyServerpassword) ?? "";
      ioc.CredSaveMode = (IOCredSaveMode)i.GetIntExtra(prefix + KeyServercredmode, (int)IOCredSaveMode.NoSave);
    }


    public static string GetErrorMessage(Exception e)
    {
      return ExceptionUtil.GetErrorMessage(e);
    }

    public static Bitmap DrawableToBitmap(Drawable drawable)
    {
      Bitmap bitmap = null;

      if (drawable is BitmapDrawable)
      {
        BitmapDrawable bitmapDrawable = (BitmapDrawable)drawable;
        if (bitmapDrawable.Bitmap != null)
        {
          return bitmapDrawable.Bitmap;
        }
      }

      if (drawable.IntrinsicWidth <= 0 || drawable.IntrinsicHeight <= 0)
      {
        bitmap = Bitmap.CreateBitmap(1, 1,
            Bitmap.Config.Argb8888); // Single color bitmap will be created of 1x1 pixel
      }
      else
      {
        bitmap = Bitmap.CreateBitmap(drawable.IntrinsicWidth, drawable.IntrinsicHeight, Bitmap.Config.Argb8888);
      }

      Canvas canvas = new Canvas(bitmap);
      drawable.SetBounds(0, 0, canvas.Width, canvas.Height);

      drawable.Draw(canvas);



      return bitmap;
    }

    public static Bitmap ChangeImageColor(Bitmap sourceBitmap, Color color)
    {
      Bitmap temp = Bitmap.CreateBitmap(sourceBitmap, 0, 0,
          sourceBitmap.Width, sourceBitmap.Height);
      Bitmap resultBitmap = temp.Copy(Bitmap.Config.Argb8888, true);
      Paint p = new Paint();
      ColorFilter filter = new LightingColorFilter(color.ToArgb(), 0);
      p.SetColorFilter(filter);

      Canvas canvas = new Canvas(resultBitmap);
      canvas.DrawBitmap(resultBitmap, 0, 0, p);
      return resultBitmap;
    }


    public static float convertDpToPixel(float dp, Context context)
    {
      Resources resources = context.Resources;
      DisplayMetrics metrics = resources.DisplayMetrics;
      float px = dp * metrics.Density;
      return px;
    }

    public static String GetClipboard(Context context)
    {
      Android.Content.ClipboardManager clipboardManager =
          (ClipboardManager)context.GetSystemService(Context.ClipboardService);
      var clip = clipboardManager.PrimaryClip;
      if (clip != null && clip.ItemCount > 0)
      {
        return clip.GetItemAt(0).CoerceToText(context);
      }

      return "";
    }

    public static void CopyToClipboard(Context context, String text, bool isProtected)
    {
      Android.Content.ClipboardManager clipboardManager =
          (ClipboardManager)context.GetSystemService(Context.ClipboardService);
      ClipData clipData = Android.Content.ClipData.NewPlainText("KP2A", text);
      if (isProtected)
      {
        //ClipDescription.Extras is only available since 24 
        if ((int)Build.VERSION.SdkInt >= 24)
        {
          var extras = clipData.Description.Extras ?? new Android.OS.PersistableBundle();
          extras.PutBoolean("android.content.extra.IS_SENSITIVE", true);
          clipData.Description.Extras = extras;
        }
      }

      clipboardManager.PrimaryClip = clipData;
      if (text == "")
      {
        //on some devices, adding empty text does not seem to work. Try again with some garbage.
        clipData = Android.Content.ClipData.NewPlainText("KP2A", "***");
        clipboardManager.PrimaryClip = clipData;
        //seems to work better on some devices:
        try
        {
          clipboardManager.Text = text;
        }
        catch (Exception exception)
        {
          Kp2aLog.LogUnexpectedError(exception);

        }

      }


    }

    private static readonly Regex ARC_DEVICE_PATTERN = new Regex(".+_cheets|cheets_.+");

    public static bool IsChromeOS(Context context)
    {
      return
          context.PackageManager.HasSystemFeature(
              "org.chromium.arc.device_management") // https://stackoverflow.com/a/39843396/292233
          || (Build.Device != null && ARC_DEVICE_PATTERN.IsMatch(Build.Device))
          ;
    }

    public static void GotoUrl(Context context, String url)
    {
      if (!string.IsNullOrEmpty(url))
      {

        if (url.StartsWith("androidapp://"))
        {
          string packageName = url.Substring("androidapp://".Length);
          Intent startKp2aIntent = context.PackageManager.GetLaunchIntentForPackage(packageName);
          if (startKp2aIntent != null)
          {
            startKp2aIntent.AddCategory(Intent.CategoryLauncher);
            startKp2aIntent.AddFlags(ActivityFlags.NewTask);
            context.StartActivity(startKp2aIntent);
          }
        }
        else
        {
          Uri uri = Uri.Parse(url);
          context.StartActivity(new Intent(
              url.StartsWith("tel:") ? Intent.ActionDial : Intent.ActionView,
              uri));
        }
      }
    }

    public static void GotoUrl(Context context, int resId)
    {
      GotoUrl(context, context.GetString(resId));
    }

    public static void GotoMarket(Context context)
    {
      GotoUrl(context, context.GetString(Resource.String.MarketURL) + context.PackageName);
    }

    public static bool GotoDonateUrl(Context context)
    {
      string donateUrl = context.GetString(Resource.String.donate_url,
          new Java.Lang.Object[]
          {
                    context.Resources.Configuration.Locale.Language,
                    context.PackageName
          });
      try
      {
        GotoUrl(context, donateUrl);
        return true;
      }
      catch (ActivityNotFoundException)
      {
        App.Kp2a.ShowMessage(context, Resource.String.error_failed_to_launch_link, MessageSeverity.Error);
        return false;
      }

    }

    public static String GetEditText(Activity act, int resId)
    {
      TextView te = (TextView)act.FindViewById(resId);
      System.Diagnostics.Debug.Assert(te != null);

      if (te != null)
      {
        return te.Text;
      }
      else
      {
        return "";
      }
    }

    public static void SetEditText(Activity act, int resId, String str)
    {
      TextView te = (TextView)act.FindViewById(resId);
      System.Diagnostics.Debug.Assert(te != null);

      if (te != null)
      {
        te.Text = str;
      }
    }

    /**
 * Indicates whether the specified action can be used as an intent. This
 * method queries the package manager for installed packages that can
 * respond to an intent with the specified action. If no suitable package is
 * found, this method returns false.
 *
 * @param context The application's environment.
 * @param action The Intent action to check for availability.
 *
 * @return True if an Intent with the specified action can be sent and
 *         responded to, false otherwise.
 */
    static bool IsIntentAvailable(Context context, String action, String type, List<String> categories)
    {
      PackageManager packageManager = context.PackageManager;
      Intent intent = new Intent(action);
      if (type != null)
        intent.SetType(type);
      if (categories != null)
        categories.ForEach(c => intent.AddCategory(c));
      IList<ResolveInfo> list =
          packageManager.QueryIntentActivities(intent,
              PackageInfoFlags.MatchDefaultOnly);
      foreach (ResolveInfo i in list)
        Kp2aLog.Log(i.ActivityInfo.ApplicationInfo.PackageName);
      return list.Count > 0;
    }

    /// <summary>
    /// Opens a browse dialog for selecting a file.
    /// </summary>
    /// <param name="activity">context activity</param>
    /// <param name="requestCodeBrowse">requestCode for onActivityResult</param>
    /// <param name="forSaving">if true, the file location is meant for saving</param>
    /// <param name="tryGetPermanentAccess">if true, the caller prefers a location that can be used permanently
    /// This means that ActionOpenDocument should be used instead of ActionGetContent (for not saving), as ActionGetContent
    /// is more for one-time access, but therefore allows possibly more available sources.</param>
    public static void ShowBrowseDialog(Activity activity, int requestCodeBrowse, bool forSaving,
        bool tryGetPermanentAccess)
    {
      //even though GetContent is not well supported (since Android 7, see https://commonsware.com/Android/previews/appendix-b-android-70) 
      //we still offer it.
      var loadAction = (tryGetPermanentAccess && IsKitKatOrLater)
          ? Intent.ActionOpenDocument
          : Intent.ActionGetContent;
      if ((!forSaving) &&
          (IsIntentAvailable(activity, loadAction, "*/*", new List<string> { Intent.CategoryOpenable })))
      {
        Intent i = new Intent(loadAction);
        i.SetType("*/*");
        i.AddCategory(Intent.CategoryOpenable);

        activity.StartActivityForResult(i, requestCodeBrowse);
      }
      else
      {
        if ((forSaving) && (IsKitKatOrLater))
        {
          Intent i = new Intent(Intent.ActionCreateDocument);
          i.SetType("*/*");
          i.AddCategory(Intent.CategoryOpenable);

          activity.StartActivityForResult(i, requestCodeBrowse);
        }
        else
        {
          string defaultPath = Android.OS.Environment.ExternalStorageDirectory.AbsolutePath;

          ShowInternalLocalFileChooser(activity, requestCodeBrowse, forSaving, defaultPath);
        }

      }
    }

    public static bool IsKitKatOrLater
    {
      get { return (int)Build.VERSION.SdkInt >= 19; }
    }


    public static PendingIntentFlags AddMutabilityFlag(PendingIntentFlags flags, PendingIntentFlags mutability)
    {
      if ((int)Build.VERSION.SdkInt >= 31)
        return flags | mutability;
      else return flags;
    }

    private static void ShowInternalLocalFileChooser(Activity act, int requestCodeBrowse, bool forSaving,
        string defaultPath)
    {

#if !EXCLUDE_FILECHOOSER
      string fileProviderAuthority = act.PackageName + ".android-filechooser.localfile";

      Intent i = Keepass2android.Kp2afilechooser.Kp2aFileChooserBridge.GetLaunchFileChooserIntent(act,
          fileProviderAuthority,
          defaultPath);
      if (forSaving)
        i.PutExtra("group.pals.android.lib.ui.filechooser.FileChooserActivity.save_dialog", true);

      act.StartActivityForResult(i, requestCodeBrowse);
#else
			App.Kp2a.ShowMessage(act, "File Chooser excluded!", MessageSeverity.Error);
#endif
    }

    /// <summary>
    /// Tries to extract the filename from the intent. Returns that filename or null if no success
    /// (e.g. on content-URIs in Android KitKat+). 
    /// Guarantees that the file exists.
    /// </summary>
    public static string IntentToFilename(Intent data, Context ctx)
    {
      string s = GetFilenameFromInternalFileChooser(data, ctx);
      if (!String.IsNullOrEmpty(s))
        return s;

      try
      {
        Uri uri = data.Data;
        if ((uri != null) && (uri.Scheme == "content"))
        {
          String[] col = new String[] { MediaStore.MediaColumns.Data };

          ICursor c1 = ctx.ContentResolver.Query(uri, col, null, null, null);
          c1.MoveToFirst();

          var possibleFilename = c1.GetString(0);
          if (File.Exists(possibleFilename))
            return possibleFilename;
        }
      }
      catch (Exception e)
      {
        Kp2aLog.LogUnexpectedError(e);
      }

      String filename = data.Data.Path;
      if ((String.IsNullOrEmpty(filename) || (!File.Exists(filename))))
        filename = data.DataString;
      if (File.Exists(filename))
        return filename;
      //found no valid file
      return null;
    }

    public static string GetFilenameFromInternalFileChooser(Intent data, Context ctx)
    {
#if !EXCLUDE_FILECHOOSER
      string EXTRA_RESULTS = "group.pals.android.lib.ui.filechooser.FileChooserActivity.results";
      if (data.HasExtra(EXTRA_RESULTS))
      {
        IList uris = data.GetParcelableArrayListExtra(EXTRA_RESULTS);
        Uri uri = (Uri)uris[0];
        {
          return Group.Pals.Android.Lib.UI.Filechooser.Providers.BaseFileProviderUtils.GetRealUri(ctx, uri)
              .ToString();
        }
      }

#endif
      return null;
    }


    public static bool HasActionBar(Activity activity)
    {
      //Actionbar is available since 11, but the layout has its own "pseudo actionbar" until 13
      return ((int)Android.OS.Build.VERSION.SdkInt >= 14) && (activity.ActionBar != null);
    }

    public delegate bool FileSelectedHandler(string filename);



    public class DismissListener : Java.Lang.Object, IDialogInterfaceOnDismissListener
    {
      private readonly Action _onDismiss;

      public DismissListener(Action onDismiss)
      {
        _onDismiss = onDismiss;
      }

      public void OnDismiss(IDialogInterface dialog)
      {
        _onDismiss();
      }
    }


    class CancelListener : Java.Lang.Object, IDialogInterfaceOnCancelListener
    {
      private readonly Action _onCancel;

      public CancelListener(Action onCancel)
      {
        _onCancel = onCancel;
      }

      public void OnCancel(IDialogInterface dialog)
      {
        _onCancel();
      }
    }

    public static void ShowFilenameDialog(Activity activity, Func<string, Dialog, bool> onOpen,
        Func<string, Dialog, bool> onCreate, Action onCancel, bool showBrowseButton, string defaultFilename,
        string detailsText, int requestCodeBrowse)
    {
      MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity);
      builder.SetView(activity.LayoutInflater.Inflate(Resource.Layout.file_selection_filename, null));

      if (onCancel != null)
        builder.SetOnCancelListener(new CancelListener(onCancel));
      Dialog dialog = builder.Create();
      dialog.Show();

      Button openButton = (Button)dialog.FindViewById(Resource.Id.open);
      Button createButton = (Button)dialog.FindViewById(Resource.Id.create);

      TextView enterFilenameDetails = (TextView)dialog.FindViewById(Resource.Id.label_open_by_filename_details);
      openButton.Visibility = onOpen != null ? ViewStates.Visible : ViewStates.Gone;
      createButton.Visibility = onCreate != null ? ViewStates.Visible : ViewStates.Gone;
      // Set the initial value of the filename
      EditText editFilename = (EditText)dialog.FindViewById(Resource.Id.file_filename);
      editFilename.Text = defaultFilename;
      enterFilenameDetails.Text = detailsText;
      enterFilenameDetails.Visibility = enterFilenameDetails.Text == "" ? ViewStates.Gone : ViewStates.Visible;

      // Open button
      if (onOpen != null)
        openButton.Click += (sender, args) =>
        {
          String fileName = ((EditText)dialog.FindViewById(Resource.Id.file_filename)).Text;
          if (onOpen(fileName, dialog))
            dialog.Dismiss();
        };

      // Create button
      if (onCreate != null)
        createButton.Click += (sender, args) =>
        {
          String fileName = ((EditText)dialog.FindViewById(Resource.Id.file_filename)).Text;
          if (onCreate(fileName, dialog))
            dialog.Dismiss();
        };

      Button cancelButton = (Button)dialog.FindViewById(Resource.Id.fnv_cancel);
      cancelButton.Click += delegate
      {
        dialog.Dismiss();
        if (onCancel != null)
          onCancel();
      };

      ImageButton browseButton = (ImageButton)dialog.FindViewById(Resource.Id.browse_button);
      if (!showBrowseButton)
      {
        browseButton.Visibility = ViewStates.Invisible;
      }

      browseButton.Click += (sender, evt) =>
      {
        string filename = ((EditText)dialog.FindViewById(Resource.Id.file_filename)).Text;

        Util.ShowBrowseDialog(activity, requestCodeBrowse,
                  onCreate != null, /*TODO should we prefer ActionOpenDocument here?*/ false);

      };

    }

    public static void QueryCredentials(IOConnectionInfo ioc, Action<IOConnectionInfo> afterQueryCredentials,
        Activity activity)
    {
      //Build dialog to query credentials:
      MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity);
      builder.SetTitle(activity.GetString(Resource.String.credentials_dialog_title));
      builder.SetPositiveButton(activity.GetString(Android.Resource.String.Ok), (dlgSender, dlgEvt) =>
      {
        Dialog dlg = (Dialog)dlgSender;
        string username = ((EditText)dlg.FindViewById(Resource.Id.cred_username)).Text;
        string password = ((EditText)dlg.FindViewById(Resource.Id.cred_password)).Text;
        int credentialRememberMode =
                  ((Spinner)dlg.FindViewById(Resource.Id.cred_remember_mode)).SelectedItemPosition;
        ioc.UserName = username;
        ioc.Password = password;
        ioc.CredSaveMode = (IOCredSaveMode)credentialRememberMode;
        afterQueryCredentials(ioc);
      });
      builder.SetView(activity.LayoutInflater.Inflate(Resource.Layout.url_credentials, null));
      builder.SetNeutralButton(activity.GetString(Android.Resource.String.Cancel),
          (dlgSender, dlgEvt) => { });
      Dialog dialog = builder.Create();
      dialog.Show();
      ((EditText)dialog.FindViewById(Resource.Id.cred_username)).Text = ioc.UserName;
      ((EditText)dialog.FindViewById(Resource.Id.cred_password)).Text = ioc.Password;
      ((Spinner)dialog.FindViewById(Resource.Id.cred_remember_mode)).SetSelection((int)ioc.CredSaveMode);
    }


    public static void FinishAndForward(Activity activity, Intent i)
    {
      i.SetFlags(ActivityFlags.ForwardResult);
      activity.StartActivity(i);
      activity.Finish();
    }

    public static void PrepareDonateOptionMenu(IMenu menu, Context ctx)
    {
      var donateItem = menu.FindItem(Resource.Id.menu_donate);
      if (donateItem != null)
      {
        donateItem.SetVisible(
            !PreferenceManager.GetDefaultSharedPreferences(ctx)
                .GetBoolean(ctx.GetString(Resource.String.NoDonateOption_key), false)
        );
      }
    }


    public static bool GetCloseDatabaseAfterFailedBiometricQuickUnlock(Context ctx)
    {
      return (PreferenceManager.GetDefaultSharedPreferences(ctx).GetBoolean(
          ctx.GetString(Resource.String.CloseDatabaseAfterFailedBiometricQuickUnlock_key), true));

    }



    public class InsetListener : Java.Lang.Object, IOnApplyWindowInsetsListener
    {
      private View _targetView;
      private readonly int _insetsType;
      private int _initialPaddingLeft;
      private int _initialPaddingTop;
      private int _initialPaddingRight;
      private int _initialPaddingBottom;


      public bool EnabledPaddingLeft { get; set; } = true;
      public bool EnablePaddingTop { get; set; } = true;
      public bool EnablePaddingRight { get; set; } = true;
      public bool EnablePaddingBottom { get; set; } = true;

      public InsetListener(View targetView, int insetsType)
      {
        _targetView = targetView;
        _insetsType = insetsType;
        _initialPaddingLeft = targetView.PaddingLeft;
        _initialPaddingTop = targetView.PaddingTop;
        _initialPaddingRight = targetView.PaddingRight;
        _initialPaddingBottom = targetView.PaddingBottom;
      }

      public InsetListener(View targetView) : this(targetView, WindowInsetsCompat.Type.SystemBars())
      {


      }

      public WindowInsetsCompat OnApplyWindowInsets(View v, WindowInsetsCompat insets)
      {
        var systemBarsInsets = insets.GetInsets(_insetsType);

        _targetView.SetPadding(
            _initialPaddingLeft + (EnabledPaddingLeft ? systemBarsInsets.Left : 0),
            _initialPaddingTop + (EnablePaddingTop ? systemBarsInsets.Top : 0),
            _initialPaddingRight + (EnablePaddingRight ? systemBarsInsets.Right : 0),
            _initialPaddingBottom + (EnablePaddingBottom ? systemBarsInsets.Bottom : 0)
        );


        return insets;
      }

      public static InsetListener ForBottomElement(View view)
      {
        return new InsetListener(view, WindowInsetsCompat.Type.Ime() | WindowInsetsCompat.Type.SystemBars() | WindowInsetsCompat.Type.DisplayCutout())
        {
          EnablePaddingTop = false
        };
      }
      public static InsetListener ForTopElement(View view)
      {
        return new InsetListener(view, WindowInsetsCompat.Type.SystemBars() | WindowInsetsCompat.Type.DisplayCutout())
        {
          EnablePaddingBottom = false
        };
      }

      public void Apply()
      {
        ViewCompat.SetOnApplyWindowInsetsListener(_targetView, this);

      }
    }

    public static void MoveBottomBarButtons(int btn1Id, int btn2Id, int bottomBarId, Activity context)
    {
      var btn1 = context.FindViewById<Button>(btn1Id);
      var btn2 = context.FindViewById<Button>(btn2Id);
      var rl = context.FindViewById<RelativeLayout>(bottomBarId);
      rl.ViewTreeObserver.GlobalLayout += (sender, args) =>
      {
        if (btn1.Width + btn2.Width > rl.Width)
        {
          btn2.SetPadding(btn2.PaddingLeft, (int)Util.convertDpToPixel(40, context), btn2.PaddingRight,
                    btn2.PaddingBottom);
        }
      };
    }

    public static MemoryStream StreamToMemoryStream(Stream stream)
    {

      var memoryStream = stream as MemoryStream;
      if (memoryStream == null)
      {
        // Read the stream into memory
        int capacity = 4096; // Default initial capacity, if stream can't report it.
        if (stream.CanSeek)
        {
          capacity = (int)stream.Length;
        }

        memoryStream = new MemoryStream(capacity);
        stream.CopyTo(memoryStream);
        stream.Close();
        memoryStream.Seek(0, SeekOrigin.Begin);
      }

      return memoryStream;

    }

    public static Bitmap MakeLargeIcon(Bitmap unscaled, Context context)
    {
      int height =
          (int)(0.9 * context.Resources.GetDimension(Android.Resource.Dimension.NotificationLargeIconHeight));
      int width = (int)(0.9 *
                        context.Resources.GetDimension(Android.Resource.Dimension.NotificationLargeIconWidth));
      return Bitmap.CreateScaledBitmap(unscaled, width, height, true);
    }

    public static string GetProtocolId(IOConnectionInfo ioc)
    {
      string displayPath = App.Kp2a.GetFileStorage(ioc).GetDisplayName(ioc);
      int protocolSeparatorPos = displayPath.IndexOf("://", StringComparison.Ordinal);
      string protocolId = protocolSeparatorPos < 0 ? "file" : displayPath.Substring(0, protocolSeparatorPos);
      return protocolId;
    }

    public static void MakeSecureDisplay(Activity context)
    {
      if (SecureDisplayConfigured(context) && !PreferenceManager.GetDefaultSharedPreferences(context)
              .GetBoolean("no_secure_display_check", false))
      {
        var hasUnsecureDisplay = HasUnsecureDisplay(context);
        if (hasUnsecureDisplay)
        {
          Kp2aLog.Log("Display is not secure");
          var intent = new Intent(context, typeof(NoSecureDisplayActivity));
          intent.AddFlags(ActivityFlags.SingleTop | ActivityFlags.ClearTop);
          context.StartActivityForResult(intent, 9999);
        }
        Kp2aLog.Log("Setting FLAG_SECURE.");
        context.Window.SetFlags(WindowManagerFlags.Secure, WindowManagerFlags.Secure);
      }
      else Kp2aLog.Log("Secure display disabled by user preference.");
    }

    public static bool SecureDisplayConfigured(Activity context)
    {
      return PreferenceManager.GetDefaultSharedPreferences(context).GetBoolean(
          context.GetString(Resource.String.ViewDatabaseSecure_key), true);
    }

    public static bool HasUnsecureDisplay(Activity context)
    {
      bool hasUnsecureDisplay = false;
      if ((int)Build.VERSION.SdkInt >= 17)
      {
        foreach (var display in ((DisplayManager)context.GetSystemService(Context.DisplayService))
                 .GetDisplays())
        {
          if ((display.Flags & DisplayFlags.Secure) == 0)
          {
            hasUnsecureDisplay = true;
          }
        }
      }

      return hasUnsecureDisplay;
    }

    public static void SetNoPersonalizedLearning(EditText editText)
    {
      if (editText == null)
        return;
      if ((int)Build.VERSION.SdkInt >= 26)
        editText.ImeOptions = (ImeAction)EditorInfoCompat.ImeFlagNoPersonalizedLearning;
      ;

    }

    public static void SetNoPersonalizedLearning(View view)
    {
      if (view is ViewGroup vg)
      {
        for (int i = 0; i < vg.ChildCount; i++)
        {
          SetNoPersonalizedLearning(vg.GetChildAt(i));
        }
      }

      if (view is EditText editText)
      {
        SetNoPersonalizedLearning(editText);
      }
    }

    public static void SetNextFreeUrlField(PwEntry entry, string url)
    {
      string prefix = url.StartsWith(KeePass.AndroidAppScheme) ? "AndroidApp" : "KP2A_URL_";
      int c = 1;
      while (entry.Strings.Get(prefix + c) != null)
      {
        c++;
      }

      entry.Strings.Set(prefix + c, new ProtectedString(false, url));
    }
  }
}

